{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Question Answering\n",
    "\n",
    "This model shows a simple form of question answering. The goal of this model is to build a network that will output answers to questions based on supplied statements as described in the book. You will bind two features (color and shape) by circular convolution. Then you will provide a cue to the model to determine either one of the features by deconvolution.\n",
    "\n",
    "**Note: Simplified method of building the model using the spa (semantic pointer architecture) package in Nengo 2.0  is shown in the last few sections of this notebook. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Setup the environment\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "%matplotlib inline\n",
    "\n",
    "import nengo\n",
    "from nengo.spa import Vocabulary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create the model\n",
    "This model has parameters as described in the book, with the ensembles having 300 neurons in 20 dimensions and the bind and unbind networks having 70 neurons per dimension. The model relies on SPA (Semantic Pointer Architecture) methods for charachterizing representation, transformation and control.\n",
    "\n",
    "Depending on your computer, this model may run slowly which can be avoided by several ways as discussed in the book. In order to run the model in direct mode (explained in the book), the parameter 'neuron_type' should be set to nengo.Direct() while creating the ensembles.\n",
    "\n",
    "As mentioned in the book, the color input will alternate every 0.5 seconds between `RED` and `BLUE`. In the same way the shape input alternates between `CIRCLE` and `SQUARE`. Thus, the model will bind alternatingly `RED * CIRCLE` and `BLUE * SQUARE` for 0.5 seconds each. In parallel you will ask a question from the model by providing a cue which will be used for deconvolving bound semantic pointers to determine an answer. For example, when the cue is `CIRCLE` the model will respond with `RED`. The cue will cycle through `CIRCLE`, `RED`, `SQUARE`, and `BLUE` within one second. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "dim = 20       # Number of dimensions \n",
    "N_input = 300  # Number of neurons in population\n",
    "N_conv = 70    # Number of neurons per dimension in bind/unbind populations\n",
    "\n",
    "rng = np.random.RandomState(7)\n",
    "vocab = Vocabulary(dimensions=dim, rng=rng, max_similarity=0.1)\n",
    "model = nengo.Network(label='Question Answering', seed=7)\n",
    "\n",
    "with model:\n",
    "    # Ensembles\n",
    "    A = nengo.Ensemble(n_neurons=N_input, dimensions=dim, label='A')\n",
    "    B = nengo.Ensemble(n_neurons=N_input, dimensions=dim, label='B')\n",
    "    C = nengo.Ensemble(n_neurons=N_input, dimensions=dim, label='C')\n",
    "    D = nengo.Ensemble(n_neurons=N_input, dimensions=dim, label='D')\n",
    "    E = nengo.Ensemble(n_neurons=N_input, dimensions=dim, label='E') \n",
    "    \n",
    "    # Creating the bind network\n",
    "    bind = nengo.networks.CircularConvolution(n_neurons=N_conv, dimensions=dim)\n",
    "    nengo.Connection(A, bind.A)\n",
    "    nengo.Connection(B, bind.B)\n",
    "    nengo.Connection(bind.output, D) \n",
    "    \n",
    "    # Creating the Unbind network\n",
    "    unbind = nengo.networks.CircularConvolution(n_neurons=N_conv, dimensions=dim, invert_a=True)\n",
    "    nengo.Connection(C, unbind.A)\n",
    "    nengo.Connection(D, unbind.B)\n",
    "    nengo.Connection(unbind.output, E)\n",
    "\n",
    "    # Getting semantic pointer values\n",
    "    CIRCLE = vocab.parse('CIRCLE').v\n",
    "    BLUE = vocab.parse('BLUE').v\n",
    "    RED = vocab.parse('RED').v\n",
    "    SQUARE = vocab.parse('SQUARE').v\n",
    "    ZERO = [0] * dim\n",
    "    \n",
    "    # function for providing color input\n",
    "    def color_input(t):\n",
    "        if (t // 0.5) % 2 == 0:\n",
    "            return RED\n",
    "        else:\n",
    "            return BLUE\n",
    "        \n",
    "    # function for providing shape input\n",
    "    def shape_input(t):\n",
    "        if (t // 0.5) % 2 == 0:\n",
    "            return CIRCLE\n",
    "        else:\n",
    "            return SQUARE\n",
    "\n",
    "    # function for providing the cue\n",
    "    def cue_input(t):\n",
    "        sequence = [ZERO, CIRCLE, RED, ZERO, SQUARE, BLUE]\n",
    "        idx = int((t // (1. / len(sequence))) % len(sequence))\n",
    "        return sequence[idx]\n",
    "      \n",
    "    # Defining inputs\n",
    "    inputA = nengo.Node(output=color_input, size_out=dim)\n",
    "    inputB = nengo.Node(output=shape_input, size_out=dim)\n",
    "    inputC = nengo.Node(output=cue_input, size_out=dim)\n",
    "    \n",
    "    # Connecting input to ensembles\n",
    "    nengo.Connection(inputA, A)\n",
    "    nengo.Connection(inputB, B)\n",
    "    nengo.Connection(inputC, C)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Add Probes to Collect Data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Anything that is probed will collect the data it produces over time, allowing you to analyze and visualize it later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "with model:\n",
    "    A_probe = nengo.Probe(A, synapse=0.1)\n",
    "    B_probe = nengo.Probe(B, synapse=0.1)\n",
    "    C_probe = nengo.Probe(C, synapse=0.1)\n",
    "    D_probe = nengo.Probe(D, synapse=0.1)\n",
    "    E_probe = nengo.Probe(E, synapse=0.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Run the model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to run the model without nengo_gui, you have to create a simulator. Then, you can run that simulator over and over again without affecting the original model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "with nengo.Simulator(model) as sim:  # Create the simulator\n",
    "    sim.run(3.0)                     # Run it for 3 seconds"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot the results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 10))\n",
    "\n",
    "plt.subplot(5, 1, 1)\n",
    "plt.plot(sim.trange(), nengo.spa.similarity(sim.data[A_probe], vocab))\n",
    "plt.legend(vocab.keys, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.0, fontsize=9)\n",
    "plt.ylabel(\"color\");\n",
    "\n",
    "plt.subplot(5, 1, 2)\n",
    "plt.plot(sim.trange(), nengo.spa.similarity(sim.data[B_probe], vocab))\n",
    "plt.legend(vocab.keys, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.0, fontsize=9)\n",
    "plt.ylabel(\"shape\");\n",
    "\n",
    "plt.subplot(5, 1, 3)\n",
    "plt.plot(sim.trange(), nengo.spa.similarity(sim.data[C_probe], vocab))\n",
    "plt.legend(vocab.keys, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.0, fontsize=9)\n",
    "plt.ylabel(\"cue\");\n",
    "\n",
    "plt.subplot(5, 1, 4)\n",
    "for pointer in ['RED * CIRCLE', 'BLUE * SQUARE']:\n",
    "    plt.plot(sim.trange(), vocab.parse(pointer).dot(sim.data[D_probe].T), label=pointer)\n",
    "plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.0,fontsize=9)\n",
    "plt.ylabel(\"bound\");\n",
    "\n",
    "plt.subplot(5, 1, 5)\n",
    "plt.plot(sim.trange(), nengo.spa.similarity(sim.data[E_probe], vocab))\n",
    "plt.legend(vocab.keys, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.0, fontsize=9)\n",
    "plt.ylabel(\"Output\")\n",
    "plt.xlabel(\"time [s]\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The last graph shows that the output is most similar to the semantic pointer bound to the current cue. For example, when `RED` and `CIRCLE` are being bound and the cue is `CIRCLE`, the output is most similar to `RED`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create the model using the `spa` package"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we will build this model again, using the spa (semantic pointer architecture) package built into Nengo 2.0. You will see that using the spa package considerably simplifies model construction and visualization through nengo_gui."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import nengo\n",
    "import nengo.spa as spa\n",
    "from nengo.spa import Vocabulary\n",
    "import numpy as np\n",
    "\n",
    "D = 32  # the dimensionality of the vectors\n",
    "rng = np.random.RandomState(7)\n",
    "vocab = Vocabulary(dimensions=D, rng=rng, max_similarity=0.1)\n",
    "\n",
    "# Adding semantic pointers to the vocabulary\n",
    "CIRCLE = vocab.parse('CIRCLE')\n",
    "BLUE = vocab.parse('BLUE')\n",
    "RED = vocab.parse('RED')\n",
    "SQUARE = vocab.parse('SQUARE')\n",
    "ZERO = vocab.add('ZERO', [0]*D)\n",
    "\n",
    "model = spa.SPA(label=\"Question Answering\", vocabs=[vocab])\n",
    "with model:\n",
    "    model.A = spa.State(D)\n",
    "    model.B = spa.State(D)\n",
    "    model.C = spa.State(D)\n",
    "    model.D = spa.State(D)\n",
    "    model.E = spa.State(D)\n",
    "\n",
    "    actions = spa.Actions(\n",
    "        'D = A * B',\n",
    "        'E = D * ~C',\n",
    "    )\n",
    " \n",
    "    model.cortical = spa.Cortical(actions)\n",
    "    \n",
    "    # function for providing color input\n",
    "    def color_input(t):\n",
    "        if (t // 0.5) % 2 == 0:\n",
    "            return 'RED'\n",
    "        else:\n",
    "            return 'BLUE'\n",
    "        \n",
    "    # function for providing shape input\n",
    "    def shape_input(t):\n",
    "        if (t // 0.5) % 2 == 0:\n",
    "            return 'CIRCLE'\n",
    "        else:\n",
    "            return 'SQUARE'\n",
    "\n",
    "    # function for providing the cue\n",
    "    def cue_input(t):\n",
    "        sequence = ['ZERO', 'CIRCLE', 'RED', 'ZERO', 'SQUARE', 'BLUE']\n",
    "        idx = int((t // (1. / len(sequence))) % len(sequence))\n",
    "        return sequence[idx]\n",
    "    \n",
    "    # Inputs\n",
    "    model.input = spa.Input(A=color_input, B=shape_input, C=cue_input)   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Run the model in nengo_gui"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from nengo_gui.ipython import IPythonViz\n",
    "IPythonViz(model, \"ch5-question.py.cfg\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Press the play button in the visualizer to run the simulation. You should see the graphs as shown in the figure below.\n",
    "\n",
    "Graphs `A`, `B`, `C` show the colour, shape and cue inputs respectively. Graph `E` shows that the output is most similar to the semantic pointer bound to the current cue (showin in `C`). For example, when RED and CIRCLE are being bound and the cue is CIRCLE, the output is most similar to RED.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from IPython.display import Image\n",
    "Image(filename='ch5-question.png')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  },
  "widgets": {
   "state": {},
   "version": "1.1.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
